﻿using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace Catalyst.Serialization
{
	public class Serializer
	{
		private static readonly object _lock = new object();

		public static void SerializeContract(Stream stream, object obj, Type type)
		{
			DataContractSerializer serializer = new DataContractSerializer(type);
			serializer.WriteObject(stream, obj);
		}

		public static void SerializeContract<T>(Stream stream, object obj)
		{
			SerializeContract(stream, obj, typeof(T));
		}

		public static MemoryStream SerializeContract(object obj, Type type)
		{
			MemoryStream ms = new MemoryStream();

			SerializeContract(ms, obj, type);

			return ms;
		}

		public static MemoryStream SerializeContract<T>(object obj)
		{
			return SerializeContract(obj, typeof(T));
		}

		public static void SerializeContract<T>(string saveFilePath, object obj)
		{
			lock (_lock)
			{
				using (FileStream fs = new FileStream(saveFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
				{
					SerializeContract<T>(fs, obj);
				}
			}
		}

		public static object DeserializeContract(Stream stream, Type type)
		{
			using (stream)
			{
				DataContractSerializer serializer = new DataContractSerializer(type);
				return serializer.ReadObject(stream);
			}
		}

		public static T DeserializeContract<T>(Stream stream) where T : class, new()
		{
			return (T)DeserializeContract(stream, typeof(T));
		}

		public static T DeserializeContract<T>(string filePath) where T : class, new()
		{
			FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
			return DeserializeContract<T>(fs);
		}

		public static void Serialize(Stream stream, object obj)
		{
			BinaryFormatter formatter = new BinaryFormatter();
			formatter.Serialize(stream, obj);
		}

		public static MemoryStream Serialize(object obj)
		{
			MemoryStream ms = new MemoryStream();

			Serialize(ms, obj);

			return ms;
		}

		public static void Serialize(string saveFilePath, object obj)
		{
			lock (_lock)
			{
				using (FileStream fs = new FileStream(saveFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
				{
					Serialize(fs, obj);
				}
			}
		}

		public static void SerializeWithSurrogate<TSurrogate>(Stream stream, object obj)
			where TSurrogate : ISerializationSurrogate, new()
		{
			BinaryFormatter formatter = new BinaryFormatter
			{
				SurrogateSelector = getSurrogateSelector<TSurrogate>()
			};

			formatter.Serialize(stream, obj);
		}

		public static MemoryStream SerializeWithSurrogate<TSurrogate>(object obj)
			where TSurrogate : ISerializationSurrogate, new()
		{
			MemoryStream ms = new MemoryStream();

			SerializeWithSurrogate<TSurrogate>(ms, obj);

			return ms;
		}

		public static void SerializeWithSurrogate<TSurrogate>(string saveFilePath, object obj)
			where TSurrogate : ISerializationSurrogate, new()
		{
			lock (_lock)
			{
				using (FileStream fs = new FileStream(saveFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
				{
					SerializeWithSurrogate<TSurrogate>(fs, obj);
				}
			}
		}

		public static T Deserialize<T>(Stream stream) where T : class, new()
		{
			using (stream)
			{
				BinaryFormatter formatter = new BinaryFormatter();
				object obj = formatter.Deserialize(stream);

				return (T)obj;
			}
		}

		public static T Deserialize<T>(string filePath) where T : class, new()
		{
			FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
			return Deserialize<T>(fs);
		}

		public static T DeserializeWithSurrogate<T, TSurrogate>(Stream stream)
			where T : class
			where TSurrogate : ISerializationSurrogate, new()
		{
			using (stream)
			{
				BinaryFormatter formatter = new BinaryFormatter
				{
					SurrogateSelector = getSurrogateSelector<TSurrogate>()
				};

				object obj = formatter.Deserialize(stream);

				return (T)obj;
			}
		}

		public static T DeserializeWithSurrogate<T, TSurrogate>(string filePath)
			where T : class
			where TSurrogate : ISerializationSurrogate, new()
		{
			FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
			return DeserializeWithSurrogate<T, TSurrogate>(fs);
		}

		static SurrogateSelector getSurrogateSelector<T>()
				where T : ISerializationSurrogate, new()
		{
			T surrogate = new T();

			SurrogateSelector selector = new SurrogateSelector();
			selector.AddSurrogate(typeof(T), new StreamingContext(StreamingContextStates.All), surrogate);

			return selector;
		}
	}
}
